﻿using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using DeepEarth.Core;
using DeepEarth.Map.Core.Extension;
using DeepEarth.Toolkit.Common;
using DeepEarth.Toolkit.Geometry;
using DeepEarth.Toolkit.Geometry.Data;

namespace DeepEarth.Toolkit.Controls
{
	internal class FreeDrawProcessor : DrawingProcessorBase
	{
		private readonly int _maximumLocations   = int.MaxValue;
		private readonly bool _forceClosePolygon;
		private readonly CompositeDisposable _subscriptions = new CompositeDisposable();

		// use the linestring to draw a shape
		LineString	_lineString;
		Polygon		_polygon;

		// use the linestringModel to hold the location of the points actually added
		// this is what will be added to the geometrycollection when drawing is complete
		ShapeGeometry	_freeDrawModel;
		Location		_currentDrawLocation;
		
		public FreeDrawProcessor(MapInteractionControl mapControl)
			: base(mapControl)
		{
		}

		public FreeDrawProcessor(MapInteractionControl mapControl, int maximumPoints)
			: base(mapControl)
		{
			if (maximumPoints > 0)
			{
				_maximumLocations = maximumPoints + 1;
			} 
		}

		public FreeDrawProcessor(MapInteractionControl mapControl, int maximumPoints, bool forceClosePolygon)
			: this(mapControl, maximumPoints)
		{
			_forceClosePolygon = forceClosePolygon;
		}


		private static IObservable<EventPattern<MouseButtonEventArgs>> GetMouseClick(UIElement uiElement)
        {
			var mbu = uiElement.GetRoutedMouseLeftButtonUp(true);
            var mbd = uiElement.GetRoutedMouseLeftButtonDown(true);
			var mm = uiElement.GetMouseMove();

            return
                from u in mbu.SkipUntil(mbd).TakeUntil(mm).Repeat()
                select u;
        }


		protected override void InitializeProcessor()
		{
			var mouseMoveSubscription = Map.InteractionSurface
				.GetMouseMove()
				.Select(x => x.EventArgs)
				.Subscribe(OnMouseMove);

			var mouseClickSubscription = GetMouseClick(Map.AsFrameworkElement)
				.Select(x => x.EventArgs)
				.Subscribe(OnMouseClick);

			_subscriptions.Add(mouseClickSubscription);
			_subscriptions.Add(mouseMoveSubscription);

				//IObservable<IEvent<MouseButtonEventArgs>>
			//Map.OverlayPanel.MouseLeftButtonDown += DrawingTool_MouseLeftButtonDown;
			//Map.OverlayPanel.MouseMove += DrawingTool_MouseMove;
			Application.Current.RootVisual.KeyDown += DrawingTool_KeyDown;

			InitializeShapes();
		}

		private void InitializeShapes()
		{
			DrawLayer.Children.Clear();

			_freeDrawModel = new LineStringGeometry();

			_lineString = new LineString
			              {
			              	IsHitTestVisible = false,
			              	StrokeThickness  = 2,
			              	Stroke           = new SolidColorBrush(Colors.Yellow),
			              	Locations        = _freeDrawModel.Locations
			              };

			_polygon = new Polygon
			           {
			           	IsHitTestVisible = false,
			           	Fill             = new SolidColorBrush(Colors.LightGray) { Opacity = 0.2},
			           	Stroke           = new SolidColorBrush(Colors.Transparent),
			           	OuterRing        = _freeDrawModel.Locations
			           };


			DrawLayer.Children.Add(_lineString);
			DrawLayer.Children.Add(_polygon);

			_currentDrawLocation = new Location();
			_freeDrawModel.Locations.Add(_currentDrawLocation);
			
		}

		private void AbortFreeDraw()
		{
			DrawLayer.Children.Clear();
			Application.Current.RootVisual.KeyDown -= DrawingTool_KeyDown;
		}

		private void CommitFreeDraw()
		{
			if (_freeDrawModel.Locations.Count >= 2)
			{
				if (_forceClosePolygon && !(_freeDrawModel is PolygonGeometry))
					_freeDrawModel = new PolygonGeometry {Locations = _freeDrawModel.Locations};


				if (_freeDrawModel is PolygonGeometry)
				{

					var firstLocation = _freeDrawModel.Locations.First();
					var lastLocation = _freeDrawModel.Locations.Last();

					if (firstLocation != lastLocation)
						_freeDrawModel.Locations.Add(firstLocation.Clone());
				}

				OnGeometryProduced(_freeDrawModel);
			}

			//reinitialize, will draw next shape!
			InitializeShapes();
		}

		private void OnMouseClick(MouseButtonEventArgs e)
		{
			e.Handled = true;

			if (_freeDrawModel.Locations.Count > 2)
			{
				var currentMousePosition = e.GetPosition(Map.AsFrameworkElement);

				// user has closed the shape - draw a polygon
				if (MouseOverStart(currentMousePosition))
				{
					// The model contains an extra location for drawing purposes. Remove it
					_freeDrawModel.Locations.Remove(_currentDrawLocation);

					var polyGeo = new PolygonGeometry { Locations = _freeDrawModel.Locations };

					_freeDrawModel = polyGeo;
					CommitFreeDraw();
					return;
				}

				// user has clicked on the last location that they added, complete the line
				if (MouseOverLast(currentMousePosition))
				{
					// The model contains an extra location for drawing purposes. Remove it
					_freeDrawModel.Locations.Remove(_currentDrawLocation);
					CommitFreeDraw();
					return;
				}
			}

			// continue drawing
			SyncMouseLocation(e.GetPosition(Map.AsFrameworkElement));

			_currentDrawLocation = _currentDrawLocation.Clone();
			_freeDrawModel.Locations.Add(_currentDrawLocation);

			_lineString.Refresh();
			_polygon.Refresh();

			if (_freeDrawModel.Locations.Count >= _maximumLocations)
			{
				CommitFreeDraw();
				return;
			}
		}

		private bool MouseOverStart(Point currentMousePosition)
		{
			return InLocationRadius(_freeDrawModel.Locations[0], currentMousePosition);
		}

		private bool MouseOverLast(Point currentMousePosition)
		{
			return _freeDrawModel.Locations.Count > 2 && InLocationRadius(_freeDrawModel.Locations[_freeDrawModel.Locations.Count - 2], currentMousePosition);
		}

		private void SyncMouseLocation(Point mousePoint)
		{
			var mouseLocation = Map.ViewportPointToLocation(mousePoint);
			_currentDrawLocation.Latitude = mouseLocation.Latitude;
			_currentDrawLocation.Longitude = mouseLocation.Longitude;
		}


		private void OnMouseMove(MouseEventArgs e)
		{
			var mousePoint = e.GetPosition(Map.AsFrameworkElement);
			SyncMouseLocation(mousePoint);

			if (_freeDrawModel.Locations.Count > 2)
			{
				if (MouseOverStart(mousePoint))
				{
					_polygon.Effect = FinishShapeEffect;
					_lineString.Effect = FinishShapeEffect;
				}
				else if (MouseOverLast(mousePoint))
				{
					_lineString.Effect = FinishShapeEffect;
				}
				else
				{
					_lineString.Effect = null;
					_polygon.Effect = null;
				}
			}

			_lineString.Refresh();
			_polygon.Refresh();
		}

		private Effect _finishShapeEffect;
		private Effect FinishShapeEffect
		{

			get
			{
				if (_finishShapeEffect == null)
				{
					_finishShapeEffect = new DropShadowEffect
					{
						BlurRadius = 200,
						Color = Colors.White,
						ShadowDepth = 0
					};
				}
				return _finishShapeEffect;
			}
		}

		const double PixelRadius = 5.0;
		bool InLocationRadius(Location location, Point viewportPoint)
		{
			var locationPoint = Map.LocationToViewportPoint(location);

			var difX = viewportPoint.X - locationPoint.X;
			var difY = viewportPoint.Y - locationPoint.Y;

			return Math.Sqrt(Math.Pow(difX, 2.0) + Math.Pow(difY, 2.0)) <= PixelRadius;
		}

		void DrawingTool_KeyDown(object sender, KeyEventArgs e)
		{

			switch (e.Key)
			{
				case Key.Left:
					Map.Pan(-200, 0);
					break;

				case Key.Right:
					Map.Pan(200, 0);
					break;

				case Key.Up:
					Map.Pan(0, -150);
					break;

				case Key.Down:
					Map.Pan(0, 150);
					break;

				case Key.Escape:
					AbortFreeDraw();
					break;

				default:
					CommitFreeDraw();
					break;
			}
		}


		protected override void CancelProcessor()
		{
			_subscriptions.Dispose();
			AbortFreeDraw();
		}
	}
}
